/*
图表
createtime: 2024/4/20
*/
(function ($) {
    $.fn.yqDiagram = function (opts, param) {
      if (typeof opts == 'string') {
        return $.fn.yqDiagram.methods[opts](this, param);
      }

      var $this = this;
      var opt;
      if ($this.data("yqtype") == "yqDiagram") {
        destroy($this);
      }

      opt = $.extend({}, $.fn.yqDiagram.defaults, opts);
      var $uniqueID = $this.attr("ID");
      if ($uniqueID === undefined || $uniqueID === null || $uniqueID == "") {
        $uniqueID = $.getUniqueID();
        $this.attr("ID", $uniqueID);
      }

      if (opt.width === undefined && $this.width() > 0) {
        opt.width = $this.width();
      }
      else if (typeof opt.width != "undefined") {
        $this.width(opt.width);
      }

      if (opt.height === undefined && $this.height() > 0) {
        opt.height = $this.height();
      }
      else if (typeof opt.height != "undefined") {
        $this.height(opt.height);
      }

      opt._flowstate = {};

      //变量信息
      $this.data("dagram_opt", opt);
      $this.data("yqtype", "yqDiagram");

      opt.beforeBindingTitle.call($this, function () {
        init($this);
      });

    };

    $.fn.yqDiagram.defaults = {
      value: {
        "nodes": [],
        "link": []
      },
      isEdit: true,
      beforeBindingTitle: function (callback) {
        callback();
      }, //标题绑定前
      afterBindingTitle: function () { },   //标题绑定后
    };

    $.fn.yqDiagram.methods = {
      options: function (jq) {
        return options(jq);
      },
      resize: function (jq, res) {
        var opt = options(jq);
        resize(jq, res);
      },
      isEdit: function (jq, tf) {
        isEdit(jq, tf);
      },
      destroy: function (jq) {
        var $object = jq;
        destroy($object);
      },
      setValue: function (jq, val) {
        setValue(jq, val);
      },
      clear: function (jq) {
        var opt = options(jq);
        opt.diagram.clear();
      },
      getValue: function (jq) {
        return getValue(jq);
      },
    }

    function options($object) {
      return $object.data("dagram_opt");
    }

    function destroy($object) {
      var opt = options($object);

      $object.empty();

      $object.removeData("dagram_opt");
      $object.removeData("yqtype");
    }

    function resize($object, res) {
      var opt = options($object);
      var $uniqueID = $object.attr("ID");

      if (res === undefined) {
        res = {};
      }

      if (!(res.width === undefined)) {
        opt.width = res.width;
      }
      if (!(res.height === undefined)) {
        opt.height = res.height;
      }

      $object.css({
        "width": opt.width + "px",
        "height": opt.height + "px"
      });

      opt.$diagram_div.css({
        "width": opt.width + "px",
        "height": opt.height + "px"
      });
      opt.$diagram_div.find("canvas").css({
        "width": opt.width + "px",
        "height": opt.height + "px"
      });
      opt.$diagram_div.find("div").css({
        "width": opt.width + "px",
        "height": opt.height + "px"
      });
    }

    function isEdit($object, tf) {
      var opt = options($object);
      if (tf === undefined) {
        tf = opt.isEdit;
      } else {
        opt.isEdit = tf;
      }

      opt.diagram.clearSelection();
      opt.diagram.grid.visible = tf;
      opt.diagram.isReadOnly = !tf;

      // 启用用户交互，包括选择、拖拽等  
      opt.diagram.allowSelect = tf;

      opt.diagram.contextMenu = diagramMenu($object);

      var nodes = opt.diagram.nodes;
      var it_node = nodes.iterator;
      while (it_node.next()) {
        var node = it_node.value;
        var data = node.data;
        //console.log(node.data, node);
        if (data.isGroup) {
          if (node.category == "base") {
            node.contextMenu = group_contextMenu($object); //菜单
            node.selectionAdornmentTemplate = group_selectionAdornmentTemplate($object);//选中时
          }
        } else {
          if (node.category == "block") {
            node.contextMenu = block_ContextMenu($object); //菜单
            node.selectionAdornmentTemplate = block_selectionAdornmentTemplate($object);//选中时
          } else if (node.category == "menu") {
            node.contextMenu = menu_ContextMenu($object); //菜单
            //node.selectionAdornmentTemplate = block_selectionAdornmentTemplate($object);//选中时
            if (!tf) {
              node.cursor = "pointer";
              node.click = function (e, obj) {
                var data = obj.part.data;
                console.log("Double-clicked node data:", data);
                window.open(data.url);
              };
            } else {
              node.cursor = "auto";
              node.click = null;
            }
          }
        }
      }

      var links = opt.diagram.links;
      var it = links.iterator;
      while (it.next()) {
        var link = it.value;
        //link.contextMenu = linkMenu($object);
        link.selectionAdornmentTemplate = link_selectionAdornmentTemplate($object);
        link.contextMenu = link_contextMenu($object);
      }
    }

    function form_load($object) {
      var opt = options($object);
      resize($object);
      setValue($object);
      isEdit($object);

      opt.afterBindingTitle.call($object);
    }

    function init($object) {
      var opt = options($object);
      var $uniqueID = $object.attr("ID");

      opt.$ = go.GraphObject.make;

      // Colors are predefined to allow easy manipulation of themes
      opt.colors = {
        red: '#ff3333',
        blue: '#3358ff',
        green: '#25ad23',
        magenta: '#d533ff',
        purple: '#7d33ff',
        orange: '#ff6233',
        brown: '#8e571e',
        white: '#ffffff',
        black: '#000000',
        beige: '#fffcd5',
        extralightblue: '#d5ebff',
        extralightred: '#f2dfe0',
        lightblue: '#a5d2fa',
        lightgray: '#cccccc',
        lightgreen: '#b3e6b3',
        lightred: '#fcbbbd',
      };

      var diagram_div = $uniqueID + "_Div";

      var html_ = '<div id="' + diagram_div + '" style="overflow: ;flex-grow: 1; border: none; position: relative;">';
      html_ += '</div>';

      $object.css({
        "width": "100%",
        "display": "flex",
        "justify-content": "space-between",
        "background-color": "#FCFCFC"
      });

      opt.$diagram_div = $(html_);
      $object.append(opt.$diagram_div);

      opt.diagram = new go.Diagram(diagram_div, {
        "contentAlignment": go.Spot.TopLeft,
        "grid": opt.$(go.Panel,
          'Grid', // a simple 10x10 grid
          opt.$(go.Shape, 'LineH', { stroke: 'lightgray', strokeWidth: 0.5 }),
          opt.$(go.Shape, 'LineV', { stroke: 'lightgray', strokeWidth: 0.5 })
        ),
        'draggingTool.isGridSnapEnabled': true,
        "handlesDragDropForTopLevelParts": true,
        "mouseDrop": (e) => {
          // when the selection is dropped in the diagram's background,
          // make sure the selected Parts no longer belong to any Group
          var ok = e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true);
          if (!ok) e.diagram.currentTool.doCancel();
        },
        commandHandler: opt.$(DrawCommandHandler), // support offset copy-and-paste

        //点击创建节点
        //'clickCreatingTool.archetypeNodeData': { "category": "block" , text: 'NEW NODE' }, // create a new node by double-clicking in background

        PartCreated: (e) => {
          var node = e.subject; // the newly inserted Node -- now need to snap its location to the grid
          node.location = node.location.copy().snapToGridPoint(e.diagram.grid.gridOrigin, e.diagram.grid.gridCellSize);
          setTimeout(() => {
            // and have the user start editing its text
            e.diagram.commandHandler.editTextBlock();
          }, 20);
        },
        'commandHandler.archetypeGroupData': { isGroup: true, text: 'NEW GROUP' },
        SelectionGrouped: (e) => {
          var group = e.subject;
          setTimeout(() => {
            // and have the user start editing its text
            e.diagram.commandHandler.editTextBlock();
          });
        },
        LinkRelinked: (e) => {
          // re-spread the connections of other links connected with both old and new nodes
          var oldnode = e.parameter.part;
          oldnode.invalidateConnectedLinks();
          var link = e.subject;
          if (e.diagram.toolManager.linkingTool.isForwards) {
            link.toNode.invalidateConnectedLinks();
          } else {
            link.fromNode.invalidateConnectedLinks();
          }
        },
        'undoManager.isEnabled': true,
      });

      // Node template block
      opt.diagram.nodeTemplateMap.add("block", opt.$(go.Node,
        'Auto',
        {
          locationSpot: go.Spot.Center,
          locationObjectName: 'SHAPE',
          desiredSize: new go.Size(120, 60),
          minSize: new go.Size(40, 40),
          resizable: true,
          resizeCellSize: new go.Size(20, 20),

          toolTip: block_NodeToolTip($object), //提示框
          //contextMenu: block_ContextMenu($object), //右键菜单
          //selectionAdornmentTemplate: block_selectionAdornmentTemplate($object)//选中时
        },
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
        opt.$(go.Shape,
          {
            // the border
            name: 'SHAPE',
            fill: opt.colors.white,
            cursor: 'pointer',
            portId: '',
            // fromLinkable: true,
            // toLinkable: true,
            // fromLinkableDuplicates: true,
            // toLinkableDuplicates: true,
            // fromSpot: go.Spot.AllSides,
            // toSpot: go.Spot.AllSides,
          },
          new go.Binding('figure'),
          new go.Binding('fill'),
          new go.Binding('stroke', 'color'),
          new go.Binding('strokeWidth', 'thickness'),
          new go.Binding('strokeDashArray', 'dash')
        ),
        opt.$(go.Shape, { width: 100, height: 40, strokeWidth: 0, fill: 'transparent' }),
        opt.$(go.TextBlock,
          {
            margin: 1,
            textAlign: 'center',
            overflow: go.TextOverflow.Ellipsis,
            editable: true
          },
          new go.Binding('text').makeTwoWay(),
          new go.Binding('stroke', 'color')
        ),
        // four named ports, one on each side:
        node_makePort($object, "T", go.Spot.Top, go.Spot.Top, true, true),
        node_makePort($object, "L", go.Spot.Left, go.Spot.Left, true, true),
        node_makePort($object, "R", go.Spot.Right, go.Spot.Right, true, true),
        node_makePort($object, "B", go.Spot.Bottom, go.Spot.Bottom, true, true)
      ));

      // Node template menu
      opt.diagram.nodeTemplateMap.add("menu", opt.$(go.Node, "Auto",
        {
          minSize: new go.Size(25, 40),
          resizable: true,
          resizeObjectName: "Shape",
        },
        new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
        opt.$(go.Shape, "Rectangle", {
          fill: opt.colors.white,
          stroke: "transparent",
        },
          new go.Binding('fill'),
        ),
        opt.$(go.Panel, "Vertical",
          {
            defaultAlignment: go.Spot.Center,
          },
          opt.$(go.Picture,
            {
              margin: new go.Margin(0, 0, 3, 0),
              width: 20,
              height: 20,
            },
            new go.Binding("source", "icon")),
          opt.$(go.TextBlock,
            {
              margin: new go.Margin(3, 0, 0, 0),
              alignment: go.Spot.Center,
              editable: true
            },
            new go.Binding('text').makeTwoWay(),
            new go.Binding('stroke', 'color')

          )
        ),
        // four named ports, one on each side:
        node_makePort($object, "T", go.Spot.Top, go.Spot.TopSide, true, true),
        node_makePort($object, "L", go.Spot.Left, go.Spot.LeftSide, true, true),
        node_makePort($object, "R", go.Spot.Right, go.Spot.RightSide, true, true),
        node_makePort($object, "B", go.Spot.Bottom, go.Spot.BottomSide, true, true)
      )
      );

      // Group template  
      opt.diagram.groupTemplateMap.add("base",
        opt.$(go.Group, 'Spot',
          {
            //locationObjectName: 'SHAPE',
            //resizable: true,
            layerName: 'Background',
            ungroupable: true,
            locationSpot: go.Spot.Center,
            selectionObjectName: 'BODY',
            computesBoundsAfterDrag: true,
            handlesDragDropForMembers: true,

            //鼠标拖动
            mouseDrop: function (e, grp) {
              return group_mouseDrop($object, e, grp);
            },
            avoidable: false,

            //选中
            selectionAdornmentTemplate: group_selectionAdornmentTemplate($object),
            //弹出菜单
            contextMenu: group_contextMenu($object),
          },
          new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
          //new go.Binding('desiredSize', 'size', go.Size.parse).makeTwoWay(go.Size.stringify),
          opt.$(go.Panel,
            'Auto',
            {
              name: 'BODY',
            },
            opt.$(go.Shape,
              {
                parameter1: 10,
                fill: opt.colors.white,
                strokeWidth: 2,
                cursor: 'pointer',
                fromLinkable: true,
                toLinkable: true,
                fromLinkableDuplicates: true,
                toLinkableDuplicates: true,
                fromSpot: go.Spot.AllSides,
                toSpot: go.Spot.AllSides,
              },
              new go.Binding('fill'),
              new go.Binding('stroke', 'color'),
              new go.Binding('strokeWidth', 'thickness'),
              new go.Binding('strokeDashArray', 'dash')
            ),
            opt.$(go.Placeholder, { background: 'transparent', margin: 20 })
          ),
          opt.$(go.TextBlock,
            {
              alignment: go.Spot.Top,
              alignmentFocus: go.Spot.Bottom,
              font: 'bold 12pt sans-serif',
              editable: true,
            },
            new go.Binding('text'),
            new go.Binding('stroke', 'color')
          )
        ));

      // Link template
      opt.diagram.linkTemplateMap.add("", opt.$(go.Link,
        {
          layerName: 'Foreground',
          routing: go.Routing.AvoidsNodes,
          corner: 10,
          fromShortLength: 10,
          toShortLength: 15, // assume arrowhead at "to" end, need to avoid bad appearance when path is thick
          relinkableFrom: true,
          relinkableTo: true,
          reshapable: true,
          resegmentable: true,

          selectionAdornmentTemplate: link_selectionAdornmentTemplate($object),
          contextMenu: link_contextMenu($object),
        },
        new go.Binding('fromSpot', 'fromSpot', go.Spot.parse),
        new go.Binding('toSpot', 'toSpot', go.Spot.parse),
        new go.Binding('fromShortLength', 'dir', (dir) => (dir >= 1 ? 10 : 0)),
        new go.Binding('toShortLength', 'dir', (dir) => (dir >= 1 ? 10 : 0)),
        new go.Binding('points').makeTwoWay(), // TwoWay due to user reshaping with LinkReshapingTool

        opt.$(go.Shape, { strokeWidth: 2 }, new go.Binding('stroke', 'color'), new go.Binding('strokeWidth', 'thickness'), new go.Binding('strokeDashArray', 'dash')),
        opt.$(go.Shape, // custom arrowheads to create the lifted effect
          {
            segmentIndex: 0,
            segmentOffset: new go.Point(15, 0),
            segmentOrientation: go.Orientation.Along,
            alignmentFocus: go.Spot.Right,
            figure: 'circle',
            width: 10,
            strokeWidth: 0,
          },
          new go.Binding('fill', 'color'),
          new go.Binding('visible', 'dir', (dir) => dir === 1)
        ),
        opt.$(go.Shape,
          {
            segmentIndex: -1,
            segmentOffset: new go.Point(-10, 6),
            segmentOrientation: go.Orientation.Plus90,
            alignmentFocus: go.Spot.Right,
            figure: 'triangle',
            width: 12,
            height: 12,
            strokeWidth: 0,
          },
          new go.Binding('fill', 'color'),
          new go.Binding('visible', 'dir', (dir) => dir >= 1),
          new go.Binding('width', 'thickness', (t) => 7 + 3 * t), // custom arrowhead must scale with the size of the while
          new go.Binding('height', 'thickness', (t) => 7 + 3 * t), // while remaining centered on line
          new go.Binding('segmentOffset', 'thickness', (t) => new go.Point(-15, 4 + 1.5 * t))
        ),
        opt.$(go.Shape,
          {
            segmentIndex: 0,
            segmentOffset: new go.Point(15, -6),
            segmentOrientation: go.Orientation.Minus90,
            alignmentFocus: go.Spot.Right,
            figure: 'triangle',
            width: 12,
            height: 12,
            strokeWidth: 0,
          },
          new go.Binding('fill', 'color'),
          new go.Binding('visible', 'dir', (dir) => dir === 2),
          new go.Binding('width', 'thickness', (t) => 7 + 3 * t),
          new go.Binding('height', 'thickness', (t) => 7 + 3 * t),
          new go.Binding('segmentOffset', 'thickness', (t) => new go.Point(-15, 4 + 1.5 * t))
        ),
        opt.$(go.TextBlock,
          { alignmentFocus: new go.Spot(0, 1, -4, 0), editable: true },
          new go.Binding('text').makeTwoWay(), // TwoWay due to user editing with TextEditingTool
          new go.Binding('stroke', 'color')
        )
      ));

      form_load($object);
    }

    function diagramMenu($object) {
      var opt = options($object);
      var menu = null;
      if (opt.isEdit) {
        menu =
          opt.$("ContextMenu",
            makeLinkButton($object, "添加菜单",
              function (e, obj) {
                addMenuNode($object, e, obj);
              }),
            makeLinkButton($object, "添加文本",
              function (e, obj) {
                addLabelNode($object, e, obj);
              }),

            makeLinkButtonLine($object),
            makeLinkButton($object, "clear",
              function (e, obj) {
                opt.diagram.clear();
              }),
            makeLinkButton($object, "添加组",
              function (e, obj) {
                addGroup($object, e, obj);
              }),
          );
      }

      return menu;
    }

    function menu_ContextMenu($object) {
      var opt = options($object);

      if (opt.isEdit) {
        return opt.$('ContextMenu',
          LightFillButtons($object),
          DarkColorButtons($object),
          makeLinkButton($object, "编辑",
            function (e, obj) {
              //e.diagram.commandHandler.deleteSelection();
            }),
          makeLinkButton($object, "删除", function (e, obj) {
            e.diagram.commandHandler.deleteSelection()
          }),
        );
      } else {
        return null;
      }

      var menu = null;
      if (opt.isEdit) {
        var menu =
          opt.$("ContextMenu",
            makeLinkButton($object, "添加菜单",
              function (e, obj) {
                addMenuNode($object, e, obj);
              }),
            makeLinkButton($object, "添加文本",
              function (e, obj) {
                addLabelNode($object, e, obj);
              }),
            makeLinkButtonLine($object),
            makeLinkButton($object, "删除",
              function (e, obj) { e.diagram.commandHandler.deleteSelection() }),
            makeLinkButton($object, "编辑",
              function (e, node) {
                editNode($object, e, node);
              }),
          );
      }

      return menu;
    }

    function makeLinkButton($object, text, action) {
      var opt = options($object);
      return opt.$("ContextMenuButton",
        opt.$(go.Panel, 'Horizontal', {
          defaultAlignment: go.Spot.Center,
        },
          opt.$(go.TextBlock, text),
          {
            height: 23,
            //stroke: 'lightgray',
            //fill: color,
            defaultAlignment: go.Spot.Center,
            margin: 1,
            background: 'transparent',
            mouseEnter: (e, shape) => (shape.stroke = 'dodgerblue'),
            mouseLeave: (e, shape) => (shape.stroke = 'lightgray'),
            click: action,
            contextClick: action,
          }));
    }

    function makeLinkButtonLine($object) {
      var opt = options($object);
      return opt.$(go.Shape, "Rectangle", {
        height: 1,
        stretch: go.GraphObject.Horizontal,
        stroke: "#C0C0C0",
        fill: "#C0C0C0"
      });
    }

    function addGroup($object, e, obj) {
      var opt = options($object);

      var nodedata = {
        "isGroup": true,
        "category": "base",
        "text": "New Group",
        "key": $.getShortID(),
        "loc": getAddLoc($object, e),
        "fill": "#fcbbbd",
        "thickness": 1,
        "color": "#000000"
      };

      opt.diagram.model.addNodeData(nodedata);
      var newnode = opt.diagram.findNodeForData(nodedata);
      opt.diagram.select(newnode);
      newnode.contextMenu = menu_ContextMenu($object);
    }

    function addMenuNode($object, e, obj) {
      var opt = options($object);

      var nodedata = {
        "category": "menu",
        "key": $.getShortID(),
        "text": "系统管理",
        "icon": "/images/menu.png",
        "url": "http://www.uieip.com/",
        "loc": getAddLoc($object, e),
        "size": "65 50"
      };

      opt.diagram.model.addNodeData(nodedata);
      var newnode = opt.diagram.findNodeForData(nodedata);
      opt.diagram.select(newnode);
      newnode.contextMenu = menu_ContextMenu($object);
    }

    function addLabelNode($object, e, obj) {
      var opt = options($object);

      var nodedata = {
        "category": "block",
        "text": "New Label",
        "key": $.getShortID(),
        "loc": getAddLoc($object, e),
        "color": "#3358ff",
        "fill": "white",
        "figure": "Rectangle",
        "thickness": 1
      };

      opt.diagram.model.addNodeData(nodedata);
      var newnode = opt.diagram.findNodeForData(nodedata);
      opt.diagram.select(newnode);

      newnode.contextMenu = block_ContextMenu($object); //菜单
      newnode.selectionAdornmentTemplate = block_selectionAdornmentTemplate($object);//选中时
    }

    function getAddLoc($object, e) {
      var opt = options($object);
      var docPoint = e.documentPoint.copy().offset(-45, -40);
      var s_obj = opt.diagram.findPartAt(docPoint);
      if (s_obj) {
        if (s_obj instanceof go.Node) {
          docPoint = docPoint.offset(40, 0);
        } else if (s_obj instanceof go.Link) {
          docPoint = docPoint.offset(30, 0);
        }
      }
      return docPoint.x + " " + docPoint.y;
    }


    //block 提示框
    function block_NodeToolTip($object) {
      var opt = options($object);

      return opt.$('ToolTip', // show some detailed information
        opt.$(go.Panel,
          'Vertical',
          { maxSize: new go.Size(200, NaN) }, // limit width but not height
          opt.$(go.TextBlock, { font: 'bold 10pt sans-serif', textAlign: 'center' }, new go.Binding('text')),
          opt.$(go.TextBlock, { font: '10pt sans-serif', textAlign: 'center' }, new go.Binding('text', 'details'))
        )
      );
    }

    //block右键菜单
    function block_ContextMenu($object) {
      var opt = options($object);

      if (opt.isEdit) {
        return opt.$('ContextMenu',
          opt.$('ContextMenuButton',
            opt.$(go.Panel, 'Horizontal',
              FigureButton($object, 'Rectangle'),
              FigureButton($object, 'RoundedRectangle'),
              FigureButton($object, 'Ellipse'),
              FigureButton($object, 'Diamond'))
          ),
          opt.$('ContextMenuButton',
            opt.$(go.Panel, 'Horizontal',
              FigureButton($object, 'Parallelogram2'),
              FigureButton($object, 'ManualOperation'),
              FigureButton($object, 'Procedure'),
              FigureButton($object, 'Cylinder1'))
          ),
          opt.$('ContextMenuButton',
            opt.$(go.Panel, 'Horizontal',
              FigureButton($object, 'Terminator'),
              FigureButton($object, 'CreateRequest'),
              FigureButton($object, 'Document'),
              FigureButton($object, 'TriangleDown'))
          ),
          LightFillButtons($object),
          DarkColorButtons($object),
          StrokeOptionsButtons($object),
          makeLinkButton($object, "删除",
            function (e, obj) { e.diagram.commandHandler.deleteSelection() }),
        );
      } else {
        return null;
      }
    }

    //block 选中时
    function block_selectionAdornmentTemplate($object) {
      var opt = options($object);

      if (opt.isEdit) {
        return opt.$(go.Adornment,
          'Spot',
          opt.$(go.Placeholder, { padding: 7 }),
          block_makeArrowButton($object, go.Spot.Top, 'TriangleUp'),
          block_makeArrowButton($object, go.Spot.Left, 'TriangleLeft'),
          block_makeArrowButton($object, go.Spot.Right, 'TriangleRight'),
          block_makeArrowButton($object, go.Spot.Bottom, 'TriangleDown'),
          CMButton($object, { alignment: new go.Spot(0.75, 0) })
        );
      } else {
        return null;
      }
    }

    // Node begin
    //复制添加
    function block_makeArrowButton($object, spot, fig) {
      var opt = options($object);

      var maker = (e, shape) => {
        e.handled = true;
        e.diagram.model.commit((m) => {
          var selnode = shape.part.adornedPart;
          // create a new node in the direction of the spot
          var p = new go.Point().setRectSpot(selnode.actualBounds, spot);
          p.subtract(selnode.location);
          p.scale(2, 2);
          p.x += Math.sign(p.x) * 30;
          p.y += Math.sign(p.y) * 30;
          p.add(selnode.location);
          p.snapToGridPoint(e.diagram.grid.gridOrigin, e.diagram.grid.gridCellSize);
          // make the new node a copy of the selected node
          var nodedata = m.copyNodeData(selnode.data);
          // add to same group as selected node
          m.setGroupKeyForNodeData(nodedata, m.getGroupKeyForNodeData(selnode.data));
          m.addNodeData(nodedata); // add to model
          // create a link from the selected node to the new node
          var linkdata = { from: selnode.key, to: m.getKeyForNodeData(nodedata) };
          m.addLinkData(linkdata); // add to model
          // move the new node to the computed location, select it, and start to edit it
          var newnode = e.diagram.findNodeForData(nodedata);
          newnode.location = p;
          e.diagram.select(newnode);
          setTimeout(() => {
            e.diagram.commandHandler.editTextBlock();
          }, 20);
        });
      };
      return opt.$(go.Shape, {
        figure: fig,
        alignment: spot,
        alignmentFocus: spot.opposite(),
        width: spot.equals(go.Spot.Top) || spot.equals(go.Spot.Bottom) ? 25 : 18,
        height: spot.equals(go.Spot.Top) || spot.equals(go.Spot.Bottom) ? 18 : 25,
        fill: 'orange',
        stroke: opt.colors.white,
        strokeWidth: 4,
        mouseEnter: (e, shape) => (shape.fill = 'dodgerblue'),
        mouseLeave: (e, shape) => (shape.fill = 'orange'),
        isActionable: true, // needed because it's in an Adornment
        click: maker,
        contextClick: maker,
      });
    }

    //... 按钮，启动编辑页面
    function CMButton($object, _options) {
      var opt = options($object);

      return opt.$(go.Shape,
        {
          fill: 'orange',
          stroke: 'rgba(0, 0, 0, 0)',
          strokeWidth: 15,
          background: 'transparent',
          geometryString: 'F1 M0 0 b 0 360 -4 0 4 z M10 0 b 0 360 -4 0 4 z M20 0 b 0 360 -4 0 4', // M10 0 A2 2 0 1 0 14 10 M20 0 A2 2 0 1 0 24 10,
          isActionable: true,
          cursor: 'context-menu',
          mouseEnter: (e, shape) => (shape.fill = 'dodgerblue'),
          mouseLeave: (e, shape) => (shape.fill = 'orange'),
          click: (e, shape) => {
            e.diagram.commandHandler.showContextMenu(shape.part.adornedPart);
          },
        },
        _options || {}
      );
    }


    function ClickFunction($object, propname, value) {
      var opt = options($object);

      return (e, obj) => {
        e.handled = true; // don't let the click bubble up
        e.diagram.model.commit((m) => {
          m.set(obj.part.adornedPart.data, propname, value);
        });
      };
    }

    // Create a context menu button for setting a data property with a color value.
    function ColorButton($object, color, propname) {
      var opt = options($object);

      if (!propname) propname = 'color';
      return opt.$(go.Shape, {
        width: 16,
        height: 16,
        stroke: 'lightgray',
        fill: color,
        margin: 1,
        background: 'transparent',
        mouseEnter: (e, shape) => (shape.stroke = 'dodgerblue'),
        mouseLeave: (e, shape) => (shape.stroke = 'lightgray'),
        click: ClickFunction($object, propname, color),
        contextClick: ClickFunction($object, propname, color),
      });
    }

    function LightFillButtons($object) {
      var opt = options($object);

      // used by multiple context menus
      return [
        opt.$('ContextMenuButton',
          opt.$(go.Panel,
            'Horizontal',
            ColorButton($object, opt.colors.white, 'fill'),
            ColorButton($object, opt.colors.beige, 'fill'),
            ColorButton($object, opt.colors.extralightblue, 'fill'),
            ColorButton($object, opt.colors.extralightred, 'fill')
          )
        ),
        opt.$('ContextMenuButton',
          opt.$(go.Panel,
            'Horizontal',
            ColorButton($object, opt.colors.lightgray, 'fill'),
            ColorButton($object, opt.colors.lightgreen, 'fill'),
            ColorButton($object, opt.colors.lightblue, 'fill'),
            ColorButton($object, opt.colors.lightred, 'fill')
          )
        ),
      ];
    }

    function DarkColorButtons($object) {
      var opt = options($object);
      // used by multiple context menus
      return [
        opt.$('ContextMenuButton',
          opt.$(go.Panel, 'Horizontal',
            ColorButton($object, opt.colors.black),
            ColorButton($object, opt.colors.green),
            ColorButton($object, opt.colors.blue),
            ColorButton($object, opt.colors.red))
        ),
        opt.$('ContextMenuButton',
          opt.$(go.Panel, 'Horizontal',
            ColorButton($object, opt.colors.white),
            ColorButton($object, opt.colors.magenta),
            ColorButton($object, opt.colors.purple),
            ColorButton($object, opt.colors.orange))
        ),
      ];
    }

    // Create a context menu button for setting a data property with a stroke width value.
    function ThicknessButton($object, sw, propname) {
      var opt = options($object);

      if (!propname) propname = 'thickness';
      return opt.$(go.Shape, 'LineH', {
        width: 16,
        height: 16,
        strokeWidth: sw,
        margin: 1,
        background: 'transparent',
        mouseEnter: (e, shape) => (shape.background = 'dodgerblue'),
        mouseLeave: (e, shape) => (shape.background = 'transparent'),
        click: ClickFunction($object, propname, sw),
        contextClick: ClickFunction($object, propname, sw),
      });
    }

    // Create a context menu button for setting a data property with a stroke dash Array value.
    function DashButton($object, dash, propname) {
      var opt = options($object);

      if (!propname) propname = 'dash';
      return opt.$(go.Shape, 'LineH', {
        width: 24,
        height: 16,
        strokeWidth: 2,
        strokeDashArray: dash,
        margin: 1,
        background: 'transparent',
        mouseEnter: (e, shape) => (shape.background = 'dodgerblue'),
        mouseLeave: (e, shape) => (shape.background = 'transparent'),
        click: ClickFunction($object, propname, dash),
        contextClick: ClickFunction($object, propname, dash),
      });
    }

    function StrokeOptionsButtons($object) {
      var opt = options($object);

      // used by multiple context menus
      return [
        opt.$('ContextMenuButton', opt.$(go.Panel, 'Horizontal',
          ThicknessButton($object, 1),
          ThicknessButton($object, 2),
          ThicknessButton($object, 3),
          ThicknessButton($object, 4),
          ThicknessButton($object, 0))),
        opt.$('ContextMenuButton', opt.$(go.Panel, 'Horizontal',
          DashButton($object, null),
          DashButton($object, [2, 4]),
          DashButton($object, [4, 4]))),
      ];
    }

    function FigureButton($object, fig, propname) {
      var opt = options($object);

      if (!propname) propname = 'figure';
      return opt.$(go.Shape, {
        width: 32,
        height: 32,
        scale: 0.5,
        fill: 'lightgray',
        figure: fig,
        margin: 1,
        background: 'transparent',
        mouseEnter: (e, shape) => (shape.fill = 'dodgerblue'),
        mouseLeave: (e, shape) => (shape.fill = 'lightgray'),
        click: ClickFunction($object, propname, fig),
        contextClick: ClickFunction($object, propname, fig),
      });
    }

    //节点连接端点的样式
    function node_makePort($object, name, align, spot, output, input) {
      var opt = options($object);

      var horizontal = align.equals(go.Spot.Top) || align.equals(go.Spot.Bottom);
      // the port is basically just a transparent rectangle that stretches along the side of the node,
      // and becomes colored when the mouse passes over it
      return opt.$(go.Shape,
        {
          fill: "transparent",  // changed to a color in the mouseEnter event handler
          strokeWidth: 0,  // no stroke
          width: horizontal ? NaN : 4,  // if not stretching horizontally, just 8 wide
          height: !horizontal ? NaN : 4,  // if not stretching vertically, just 8 tall
          alignment: align,  // align the port on the main Shape
          stretch: (horizontal ? go.GraphObject.Horizontal : go.GraphObject.Vertical),
          portId: name,  // declare this object to be a "port"
          fromSpot: spot,  // declare where links may connect at this port
          fromLinkable: output,  // declare whether the user may draw links from here
          toSpot: spot,  // declare where links may connect at this port
          toLinkable: input,  // declare whether the user may draw links to here
          cursor: "pointer",  // show a different cursor to indicate potential link point
          mouseEnter: (e, port) => {  // the PORT argument will be this Shape
            if (!e.diagram.isReadOnly) port.fill = "rgba(255,0,255,0.5)";
          },
          mouseLeave: (e, port) => port.fill = "transparent"
        });
    }

    // Node end

    // group begin       
    //鼠标拖动
    function group_mouseDrop($object, e, grp) {
      var opt = options($object);
      var ok = grp.addMembers(grp.diagram.selection, true);
      if (!ok) grp.diagram.currentTool.doCancel();
    }

    //选中
    function group_selectionAdornmentTemplate($object) {
      var opt = options($object);

      return opt.$(go.Adornment,
        'Spot',
        opt.$(go.Panel, 'Auto', opt.$(go.Shape, { fill: null, stroke: 'dodgerblue', strokeWidth: 3 }), opt.$(go.Placeholder, { margin: 1.5 })),
        CMButton($object, { alignment: go.Spot.TopRight, alignmentFocus: go.Spot.BottomRight })
      );
    }

    //弹出菜单
    function group_contextMenu($object) {
      var opt = options($object);
      if (opt.isEdit) {
        return opt.$('ContextMenu',
          LightFillButtons($object),
          DarkColorButtons($object),
          StrokeOptionsButtons($object)
        );
      } else {
        return null;
      }
    }
    // group end

    //link begin
    //link 选择
    function link_selectionAdornmentTemplate($object) {
      var opt = options($object);

      if (!opt.isEdit) {
        return null;
      }

      return opt.$(go.Adornment, // use a special selection Adornment that does not obscure the link path itself
        opt.$(go.Shape,
          {
            // this uses a pathPattern with a gap in it, in order to avoid drawing on top of the link path Shape
            isPanelMain: true,
            stroke: 'transparent',
            strokeWidth: 6,
            pathPattern: link_makeAdornmentPathPattern($object, 2), // == thickness or strokeWidth
          },
          new go.Binding('pathPattern', 'thickness', link_makeAdornmentPathPattern($object, 0))
        ),
        CMButton($object, { alignmentFocus: new go.Spot(0, 0, -6, -4) })
      );
    }

    function link_makeAdornmentPathPattern($object, w) {
      var opt = options($object);

      return opt.$(go.Shape, {
        stroke: 'dodgerblue',
        strokeWidth: 2,
        strokeCap: 'square',
        geometryString: 'M0 0 M4 2 H3 M4 ' + (w + 4).toString() + ' H3',
      });
    }

    // Link context menu
    function link_contextMenu($object) {
      var opt = options($object);

      if (!opt.isEdit) {
        return null;
      }

      return opt.$('ContextMenu',
        DarkColorButtons($object),
        StrokeOptionsButtons($object),
        opt.$('ContextMenuButton', opt.$(go.Panel, 'Horizontal', ArrowButton($object, 0), ArrowButton($object, 1), ArrowButton($object, 2))),
        opt.$('ContextMenuButton',
          opt.$(go.Panel,
            'Horizontal',
            opt.$(go.Panel,
              'Spot',
              AllSidesButton($object, false),
              SpotButton($object, go.Spot.Top, false),
              SpotButton($object, go.Spot.Left, false),
              SpotButton($object, go.Spot.Right, false),
              SpotButton($object, go.Spot.Bottom, false)
            ),
            opt.$(go.Panel,
              'Spot',
              { margin: new go.Margin(0, 0, 0, 2) },
              AllSidesButton($object, true),
              SpotButton($object, go.Spot.Top, true),
              SpotButton($object, go.Spot.Left, true),
              SpotButton($object, go.Spot.Right, true),
              SpotButton($object, go.Spot.Bottom, true)
            )
          )
        )
      );
    }

    function ArrowButton($object, num) {
      var opt = options($object);

      var geo = 'M0 0 M8 16 M0 8 L16 8  M12 11 L16 8 L12 5';
      if (num === 0) {
        geo = 'M0 0 M16 16 M0 8 L16 8';
      } else if (num === 2) {
        geo = 'M0 0 M16 16 M0 8 L16 8  M12 11 L16 8 L12 5  M4 11 L0 8 L4 5';
      }
      return opt.$(go.Shape, {
        geometryString: geo,
        margin: 2,
        background: 'transparent',
        mouseEnter: (e, shape) => (shape.background = 'dodgerblue'),
        mouseLeave: (e, shape) => (shape.background = 'transparent'),
        click: ClickFunction($object, 'dir', num),
        contextClick: ClickFunction($object, 'dir', num),
      });
    }

    function AllSidesButton($object, to) {
      var opt = options($object);

      var setter = (e, shape) => {
        e.handled = true;
        e.diagram.model.commit((m) => {
          var link = shape.part.adornedPart;
          m.set(link.data, to ? 'toSpot' : 'fromSpot', go.Spot.stringify(go.Spot.AllSides));
          // re-spread the connections of other links connected with the node
          (to ? link.toNode : link.fromNode).invalidateConnectedLinks();
        });
      };
      return opt.$(go.Shape, {
        width: 12,
        height: 12,
        fill: 'transparent',
        mouseEnter: (e, shape) => (shape.background = 'dodgerblue'),
        mouseLeave: (e, shape) => (shape.background = 'transparent'),
        click: setter,
        contextClick: setter,
      });
    }

    function SpotButton($object, spot, to) {
      var opt = options($object);

      var ang = 0;
      var side = go.Spot.RightSide;
      if (spot.equals(go.Spot.Top)) {
        ang = 270;
        side = go.Spot.TopSide;
      } else if (spot.equals(go.Spot.Left)) {
        ang = 180;
        side = go.Spot.LeftSide;
      } else if (spot.equals(go.Spot.Bottom)) {
        ang = 90;
        side = go.Spot.BottomSide;
      }
      if (!to) ang -= 180;
      var setter = (e, shape) => {
        e.handled = true;
        e.diagram.model.commit((m) => {
          var link = shape.part.adornedPart;
          m.set(link.data, to ? 'toSpot' : 'fromSpot', go.Spot.stringify(side));
          // re-spread the connections of other links connected with the node
          (to ? link.toNode : link.fromNode).invalidateConnectedLinks();
        });
      };
      return opt.$(go.Shape, {
        alignment: spot,
        alignmentFocus: spot.opposite(),
        geometryString: 'M0 0 M12 12 M12 6 L1 6 L4 4 M1 6 L4 8',
        angle: ang,
        background: 'transparent',
        mouseEnter: (e, shape) => (shape.background = 'dodgerblue'),
        mouseLeave: (e, shape) => (shape.background = 'transparent'),
        click: setter,
        contextClick: setter,
      });
    }
    //Link end

    function setValue($object, value) {
      var opt = options($object);

      if (!(value === undefined)) {
        if (typeof value == "string") {
          value = JSON.parse(value);
        }
        opt.value = value;
      }

      if (!(typeof opt.value === "object")) {
        opt.value = {
          nodes: [],
          links: []
        };
      }
      if (!Array.isArray(opt.value.nodes)) {
        opt.value.nodes = [];
      }
      if (!Array.isArray(opt.value.links)) {
        opt.value.links = [];
      }

      opt.diagram.model = go.Model.fromJson({
        "class": "GraphLinksModel",
        "nodeDataArray": opt.value.nodes,
        "linkDataArray": opt.value.links
      });
    }

    function getValue($object) {
      var opt = options($object);
      var value = JSON.parse(opt.diagram.model.toJson());
      var j_ = {
        nodes: value.nodeDataArray,
        links: value.linkDataArray
      };

      for (var i = 0; i < j_.nodes.length; i++) {
        var locs = j_.nodes[i].loc.split(" ");
        j_.nodes[i].loc = (locs[0].indexOf(".") > 0 ? parseFloat(locs[0]).toFixed(2) : locs[0])
          + " "
          + (locs[1].indexOf(".") > 0 ? parseFloat(locs[1]).toFixed(2) : locs[1]);
      }

      for (var i = 0; i < j_.links.length; i++) {
        var points = [];
        for (var j = 0; j < j_.links[i].points.length; j++) {
          var point = j_.links[i].points[j].toString();
          if (point.indexOf(".") > 0) {
            point = parseFloat(j_.links[i].points[j].toFixed(2));
          } else {
            point = j_.links[i].points[j];
          }
          points.push(point);
        }
        j_.links[i].points = points;
      }

      return j_;
    }
  })(jQuery);
